import logger from "./utils/logger.js";

const BASE_DELAY = 3000;

const outdatedLinks = new WeakSet<HTMLLinkElement>();

function connect() {
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
  const ws = new WebSocket(`${protocol}//${window.location.host}`);

  ws.onopen = () => {
    logger.log("HMR Connected to server");
    delay = BASE_DELAY;
  };

  ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.type === "change") {
      logger.log("File", message.path, "has been changed");

      // html change then reload
      if (message.path.endsWith("index.html")) {
        window.location.reload();
        return;
      }

      // patch new js file
      if (message.path.endsWith(".js")) {
        const oldScript = document.querySelector(
          `script[src="${message.path}"]`
        );
        if (oldScript) {
          oldScript.remove();
        }
        const script = document.createElement("script");
        script.src = message.path;
        document.head.appendChild(script);
      }

      // patch new css file
      if (message.path.endsWith(".css")) {
        // v1
        // const oldLink = document.querySelector(`link[href="${message.path}"]`);
        // if (oldLink) {
        //   oldLink.remove();
        // }
        // const link = document.createElement("link");
        // link.rel = "stylesheet";
        // link.href = message.path;
        // document.head.appendChild(link);

        // v2
        const el = Array.from(
          document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')
        ).find((link) => {
          // if the link is already outdated, skip it
          if (outdatedLinks.has(link)) {
            return false;
          }
          // if the link is not outdated, check if it is the one we are looking for
          if (link.href.includes(message.path)) {
            return true;
          }
          return false;
        });

        if (el) {
          console.log("get new CSS file", message.path, message.timestamp);

          const newLink = document.createElement("link");
          newLink.rel = "stylesheet";
          /**
           * Add a timestamp to the url to avoid cache
           */
          newLink.href = new URL(
            message.path + "?t=" + message.timestamp,
            el.href
          ).href;
          newLink.dataset.timestamp = message.timestamp;
          /**
           * If multiple new links are added at the same time
           *
           * new1
           * el.after new1
           *
           * new2
           * el.after new2
           *
           * two new links added, but onload remove the same old el
           *
           * onload new1 -> el.remove()
           * onload new2 -> el.remove()
           */
          newLink.onload = () => {
            el.remove();
            console.log("CSS file updated:", message.path, message.timestamp);
          };
          el.after(newLink);
          outdatedLinks.add(el);
        }
      }
    }
  };

  ws.onerror = (error) => {
    logger.error("WebSocket error:", error);
  };

  ws.onclose = () => {
    logger.log("HMR Connection closed");
    clearInterval(heartbeatInterval);
    // retry
    reconnect();
  };

  // heartbeat
  const heartbeatInterval = setInterval(() => {
    ws.send(JSON.stringify({ type: "heartbeat" }));
  }, 3 * 1000);
}

// for reconnect
let delay = BASE_DELAY;
function reconnect() {
  logger.log("HMR Connection closed, retry in ", delay, " ms");
  setTimeout(() => {
    connect();
  }, delay);
  delay *= 2;
}

// main entry to start initial ws
connect();
